# Copyright (c) 2016 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Library that supports Auto Configuration."""
from __future__ import absolute_import, division, print_function

import logging
import os
import re
from ipaddress import ip_address

import yaml

from vpplib.VPPUtil import VPPUtil
from vpplib.VppPCIUtil import VppPCIUtil
from vpplib.VppHugePageUtil import VppHugePageUtil
from vpplib.CpuUtils import CpuUtils
from vpplib.VppGrubUtil import VppGrubUtil
from vpplib.QemuUtils import QemuUtils

#  Python2/3 compatible
try:
    input = raw_input  # noqa
except NameError:
    pass

__all__ = ["AutoConfig"]

# Constants
MIN_SYSTEM_CPUS = 2
MIN_TOTAL_HUGE_PAGES = 1024
MAX_PERCENT_FOR_HUGE_PAGES = 70

IPERFVM_XML = "configs/iperf-vm.xml"
IPERFVM_IMAGE = "images/xenial-mod.img"
IPERFVM_ISO = "configs/cloud-config.iso"


class AutoConfig(object):
    """Auto Configuration Tools"""

    def __init__(self, rootdir, filename, clean=False):
        """
        The Auto Configure class.

        :param rootdir: The root directory for all the auto configuration files
        :param filename: The autoconfiguration file
        :param clean: When set initialize the nodes from the auto-config file
        :type rootdir: str
        :type filename: str
        :type clean: bool
        """
        self._autoconfig_filename = rootdir + filename
        self._rootdir = rootdir
        self._metadata = {}
        self._nodes = {}
        self._vpp_devices_node = {}
        self._hugepage_config = ""
        self._clean = clean
        self._loadconfig()
        self._sockfilename = ""

    def get_nodes(self):
        """
        Returns the nodes dictionary.

        :returns: The nodes
        :rtype: dictionary
        """

        return self._nodes

    @staticmethod
    def _autoconfig_backup_file(filename):
        """
        Create a backup file.

        :param filename: The file to backup
        :type filename: str
        """

        # Does a copy of the file exist, if not create one
        ofile = filename + ".orig"
        (ret, stdout, stderr) = VPPUtil.exec_command("ls {}".format(ofile))
        if ret != 0:
            logging.debug(stderr)
            if stdout.strip("\n") != ofile:
                cmd = "sudo cp {} {}".format(filename, ofile)
                (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
                if ret != 0:
                    logging.debug(stderr)

    # noinspection PyBroadException
    @staticmethod
    def _ask_user_ipv4():
        """
        Asks the user for a number within a range.
        default is returned if return is entered.

        :returns: IP address with cidr
        :rtype: str
        """

        while True:
            answer = input("Please enter the IPv4 Address [n.n.n.n/n]: ")
            try:
                ipinput = answer.split("/")
                ipaddr = ip_address(ipinput[0])
                if len(ipinput) > 1:
                    plen = answer.split("/")[1]
                else:
                    answer = input("Please enter the netmask [n.n.n.n]: ")
                    plen = ip_address(answer).netmask_bits()
                return "{}/{}".format(ipaddr, plen)
            except ValueError:
                print("Please enter a valid IPv4 address.")

    @staticmethod
    def _ask_user_range(question, first, last, default):
        """
        Asks the user for a number within a range.
        default is returned if return is entered.

        :param question: Text of a question.
        :param first: First number in the range
        :param last: Last number in the range
        :param default: The value returned when return is entered
        :type question: string
        :type first: int
        :type last: int
        :type default: int
        :returns: The answer to the question
        :rtype: int
        """

        while True:
            answer = input(question)
            if answer == "":
                answer = default
                break
            if re.findall(r"[0-9+]", answer):
                if int(answer) in range(first, last + 1):
                    break
                else:
                    print(
                        "Please a value between {} and {} or Return.".format(
                            first, last
                        )
                    )
            else:
                print(
                    "Please a number between {} and {} or Return.".format(first, last)
                )

        return int(answer)

    @staticmethod
    def _ask_user_yn(question, default):
        """
        Asks the user for a yes or no question.

        :param question: Text of a question.
        :param default: The value returned when return is entered
        :type question: string
        :type default: string
        :returns: The answer to the question
        :rtype: string
        """

        input_valid = False
        default = default.lower()
        answer = ""
        while not input_valid:
            answer = input(question)
            if answer == "":
                answer = default
            if re.findall(r"[YyNn]", answer):
                input_valid = True
                answer = answer[0].lower()
            else:
                print("Please answer Y, N or Return.")

        return answer

    def _loadconfig(self):
        """
        Load the testbed configuration, given the auto configuration file.

        """

        # Get the Topology, from the topology layout file
        topo = {}
        with open(self._autoconfig_filename, "r") as stream:
            try:
                topo = yaml.load(stream)
                if "metadata" in topo:
                    self._metadata = topo["metadata"]
            except yaml.YAMLError as exc:
                raise RuntimeError(
                    "Couldn't read the Auto config file {}.".format(
                        self._autoconfig_filename, exc
                    )
                )

        systemfile = self._rootdir + self._metadata["system_config_file"]
        if self._clean is False and os.path.isfile(systemfile):
            with open(systemfile, "r") as sysstream:
                try:
                    systopo = yaml.load(sysstream)
                    if "nodes" in systopo:
                        self._nodes = systopo["nodes"]
                except yaml.YAMLError as sysexc:
                    raise RuntimeError(
                        "Couldn't read the System config file {}.".format(
                            systemfile, sysexc
                        )
                    )
        else:
            # Get the nodes from Auto Config
            if "nodes" in topo:
                self._nodes = topo["nodes"]

        # Set the root directory in all the nodes
        for i in self._nodes.items():
            node = i[1]
            node["rootdir"] = self._rootdir

    def updateconfig(self):
        """
        Update the testbed configuration, given the auto configuration file.
        We will write the system configuration file with the current node
        information

        """

        # Initialize the yaml data
        ydata = {"metadata": self._metadata, "nodes": self._nodes}

        # Write the system config file
        filename = self._rootdir + self._metadata["system_config_file"]
        with open(filename, "w") as yamlfile:
            yaml.dump(ydata, yamlfile)

    def _update_auto_config(self):
        """
        Write the auto configuration file with the new configuration data,
        input from the user.

        """

        # Initialize the yaml data
        nodes = {}
        with open(self._autoconfig_filename, "r") as stream:
            try:
                ydata = yaml.load(stream)
                if "nodes" in ydata:
                    nodes = ydata["nodes"]
            except yaml.YAMLError as exc:
                print(exc)
                return

        for i in nodes.items():
            key = i[0]
            node = i[1]

            # Interfaces
            node["interfaces"] = {}
            for item in self._nodes[key]["interfaces"].items():
                port = item[0]
                interface = item[1]

                node["interfaces"][port] = {}
                addr = "{}".format(interface["pci_address"])
                node["interfaces"][port]["pci_address"] = addr
                if "mac_address" in interface:
                    node["interfaces"][port]["mac_address"] = interface["mac_address"]

            if "total_other_cpus" in self._nodes[key]["cpu"]:
                node["cpu"]["total_other_cpus"] = self._nodes[key]["cpu"][
                    "total_other_cpus"
                ]
            if "total_vpp_cpus" in self._nodes[key]["cpu"]:
                node["cpu"]["total_vpp_cpus"] = self._nodes[key]["cpu"][
                    "total_vpp_cpus"
                ]
            if "reserve_vpp_main_core" in self._nodes[key]["cpu"]:
                node["cpu"]["reserve_vpp_main_core"] = self._nodes[key]["cpu"][
                    "reserve_vpp_main_core"
                ]

            # TCP
            if "active_open_sessions" in self._nodes[key]["tcp"]:
                node["tcp"]["active_open_sessions"] = self._nodes[key]["tcp"][
                    "active_open_sessions"
                ]
            if "passive_open_sessions" in self._nodes[key]["tcp"]:
                node["tcp"]["passive_open_sessions"] = self._nodes[key]["tcp"][
                    "passive_open_sessions"
                ]

            # Huge pages
            node["hugepages"]["total"] = self._nodes[key]["hugepages"]["total"]

        # Write the auto config config file
        with open(self._autoconfig_filename, "w") as yamlfile:
            yaml.dump(ydata, yamlfile)

    def apply_huge_pages(self):
        """
        Apply the huge page config

        """

        for i in self._nodes.items():
            node = i[1]

            hpg = VppHugePageUtil(node)
            hpg.hugepages_dryrun_apply()

    @staticmethod
    def _apply_vpp_cpu(node):
        """
        Apply the VPP cpu config

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        # Get main core
        cpu = "\n"
        if "vpp_main_core" in node["cpu"]:
            vpp_main_core = node["cpu"]["vpp_main_core"]
        else:
            vpp_main_core = 0
        if vpp_main_core != 0:
            cpu += "  main-core {}\n".format(vpp_main_core)

        # Get workers
        vpp_workers = node["cpu"]["vpp_workers"]
        vpp_worker_len = len(vpp_workers)
        if vpp_worker_len > 0:
            vpp_worker_str = ""
            for i, worker in enumerate(vpp_workers):
                if i > 0:
                    vpp_worker_str += ","
                if worker[0] == worker[1]:
                    vpp_worker_str += "{}".format(worker[0])
                else:
                    vpp_worker_str += "{}-{}".format(worker[0], worker[1])

            cpu += "  corelist-workers {}\n".format(vpp_worker_str)

        return cpu

    @staticmethod
    def _apply_vpp_devices(node):
        """
        Apply VPP PCI Device configuration to vpp startup.

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        devices = ""
        ports_per_numa = node["cpu"]["ports_per_numa"]

        for item in ports_per_numa.items():
            value = item[1]
            interfaces = value["interfaces"]

            # if 0 was specified for the number of vpp workers, use 1 queue
            num_rx_queues = None
            num_tx_queues = None
            if "rx_queues" in value:
                num_rx_queues = value["rx_queues"]
            if "tx_queues" in value:
                num_tx_queues = value["tx_queues"]

            num_rx_desc = None
            num_tx_desc = None

            # Create the devices string
            for interface in interfaces:
                pci_address = interface["pci_address"]
                pci_address = pci_address.lstrip("'").rstrip("'")
                devices += "\n"
                devices += "  dev {} {{ \n".format(pci_address)
                if num_rx_queues:
                    devices += "    num-rx-queues {}\n".format(num_rx_queues)
                else:
                    devices += "    num-rx-queues {}\n".format(1)
                if num_tx_queues:
                    devices += "    num-tx-queues {}\n".format(num_tx_queues)
                if num_rx_desc:
                    devices += "    num-rx-desc {}\n".format(num_rx_desc)
                if num_tx_desc:
                    devices += "    num-tx-desc {}\n".format(num_tx_desc)
                devices += "  }"

        return devices

    @staticmethod
    def _apply_buffers(node):
        """
        Apply VPP PCI Device configuration to vpp startup.

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """
        buffers = ""
        total_mbufs = node["cpu"]["total_mbufs"]

        # If the total mbufs is not 0 or less than the default, set num-bufs
        logging.debug("Total mbufs: {}".format(total_mbufs))
        if total_mbufs != 0 and total_mbufs > 16384:
            buffers += "  buffers-per-numa {}".format(total_mbufs)

        return buffers

    @staticmethod
    def _calc_vpp_workers(
        node,
        vpp_workers,
        numa_node,
        other_cpus_end,
        total_vpp_workers,
        reserve_vpp_main_core,
    ):
        """
        Calculate the VPP worker information

        :param node: Node dictionary
        :param vpp_workers: List of VPP workers
        :param numa_node: Numa node
        :param other_cpus_end: The end of the cpus allocated for cores
        other than vpp
        :param total_vpp_workers: The number of vpp workers needed
        :param reserve_vpp_main_core: Is there a core needed for
        the vpp main core
        :type node: dict
        :type numa_node: int
        :type other_cpus_end: int
        :type total_vpp_workers: int
        :type reserve_vpp_main_core: bool
        :returns: Is a core still needed for the vpp main core
        :rtype: bool
        """

        # Can we fit the workers in one of these slices
        cpus = node["cpu"]["cpus_per_node"][numa_node]
        for cpu in cpus:
            start = cpu[0]
            end = cpu[1]
            if start <= other_cpus_end:
                start = other_cpus_end + 1

            if reserve_vpp_main_core:
                start += 1

            workers_end = start + total_vpp_workers - 1

            if workers_end <= end:
                if reserve_vpp_main_core:
                    node["cpu"]["vpp_main_core"] = start - 1
                reserve_vpp_main_core = False
                if total_vpp_workers:
                    vpp_workers.append((start, workers_end))
                break

        # We still need to reserve the main core
        if reserve_vpp_main_core:
            node["cpu"]["vpp_main_core"] = other_cpus_end + 1

        return reserve_vpp_main_core

    @staticmethod
    def _calc_desc_and_queues(
        total_numa_nodes, total_ports_per_numa, total_rx_queues, ports_per_numa_value
    ):
        """
        Calculate the number of descriptors and queues

        :param total_numa_nodes: The total number of numa nodes
        :param total_ports_per_numa: The total number of ports for this
        numa node
        :param total_rx_queues: The total number of rx queues / port
        :param ports_per_numa_value: The value from the ports_per_numa
        dictionary
        :type total_numa_nodes: int
        :type total_ports_per_numa: int
        :type total_rx_queues: int
        :type ports_per_numa_value: dict
        :returns The total number of message buffers
        :rtype: int
        """

        # Get the number of rx queues
        rx_queues = max(1, total_rx_queues)
        tx_queues = rx_queues * total_numa_nodes + 1

        # Get the descriptor entries
        desc_entries = 1024
        ports_per_numa_value["rx_queues"] = rx_queues
        total_mbufs = (
            (rx_queues * desc_entries) + (tx_queues * desc_entries)
        ) * total_ports_per_numa

        return total_mbufs

    @staticmethod
    def _create_ports_per_numa(node, interfaces):
        """
        Create a dictionary or ports per numa node
        :param node: Node dictionary
        :param interfaces: All the interfaces to be used by vpp
        :type node: dict
        :type interfaces: dict
        :returns: The ports per numa dictionary
        :rtype: dict
        """

        # Make a list of ports by numa node
        ports_per_numa = {}
        for item in interfaces.items():
            i = item[1]
            if i["numa_node"] not in ports_per_numa:
                ports_per_numa[i["numa_node"]] = {"interfaces": []}
                ports_per_numa[i["numa_node"]]["interfaces"].append(i)
            else:
                ports_per_numa[i["numa_node"]]["interfaces"].append(i)
        node["cpu"]["ports_per_numa"] = ports_per_numa

        return ports_per_numa

    def calculate_cpu_parameters(self):
        """
        Calculate the cpu configuration.

        """

        # Calculate the cpu parameters, needed for the
        # vpp_startup and grub configuration
        for i in self._nodes.items():
            node = i[1]

            # get total number of nic ports
            interfaces = node["interfaces"]

            # Make a list of ports by numa node
            ports_per_numa = self._create_ports_per_numa(node, interfaces)

            # Get the number of cpus to skip, we never use the first cpu
            other_cpus_start = 1
            other_cpus_end = other_cpus_start + node["cpu"]["total_other_cpus"] - 1
            other_workers = None
            if other_cpus_end != 0:
                other_workers = (other_cpus_start, other_cpus_end)
            node["cpu"]["other_workers"] = other_workers

            # Allocate the VPP main core and workers
            vpp_workers = []
            reserve_vpp_main_core = node["cpu"]["reserve_vpp_main_core"]
            total_vpp_cpus = node["cpu"]["total_vpp_cpus"]
            total_rx_queues = node["cpu"]["total_rx_queues"]

            # If total_vpp_cpus is 0 or is less than the numa nodes with ports
            #  then we shouldn't get workers
            total_workers_node = 0
            if len(ports_per_numa):
                total_workers_node = total_vpp_cpus // len(ports_per_numa)
            total_main = 0
            if reserve_vpp_main_core:
                total_main = 1
            total_mbufs = 0
            if total_main + total_workers_node != 0:
                for item in ports_per_numa.items():
                    numa_node = item[0]
                    value = item[1]

                    # Get the number of descriptors and queues
                    mbufs = self._calc_desc_and_queues(
                        len(ports_per_numa),
                        len(value["interfaces"]),
                        total_rx_queues,
                        value,
                    )
                    total_mbufs += mbufs

                    # Get the VPP workers
                    reserve_vpp_main_core = self._calc_vpp_workers(
                        node,
                        vpp_workers,
                        numa_node,
                        other_cpus_end,
                        total_workers_node,
                        reserve_vpp_main_core,
                    )

                total_mbufs *= 2.5
                total_mbufs = int(total_mbufs)
            else:
                total_mbufs = 0

            # Save the info
            node["cpu"]["vpp_workers"] = vpp_workers
            node["cpu"]["total_mbufs"] = total_mbufs

        # Write the config
        self.updateconfig()

    @staticmethod
    def _apply_vpp_tcp(node):
        """
        Apply the tcp config

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        active_open_sessions = node["tcp"]["active_open_sessions"]
        aos = int(active_open_sessions)

        passive_open_sessions = node["tcp"]["passive_open_sessions"]
        pos = int(passive_open_sessions)

        # Generate the api-segment gid vpp sheit in any case
        if (aos + pos) == 0:
            tcp = "\n".join(["api-segment {", "  gid vpp", "}"])
            return tcp.rstrip("\n")

        tcp = "\n".join(
            [
                "# TCP stack-related configuration parameters",
                "# expecting {:d} client sessions, {:d} server sessions\n".format(
                    aos, pos
                ),
                "heapsize 4g\n",
                "api-segment {",
                "  global-size 2000M",
                "  api-size 1G",
                "}\n",
                "session {",
                "  event-queue-length {:d}".format(aos + pos),
                "  preallocated-sessions {:d}".format(aos + pos),
                "  v4-session-table-buckets {:d}".format((aos + pos) // 4),
                "  v4-session-table-memory 3g\n",
            ]
        )
        if aos > 0:
            tcp = (
                tcp + "  v4-halfopen-table-buckets {:d}".format((aos + pos) // 4) + "\n"
            )
            tcp = tcp + "  v4-halfopen-table-memory 3g\n"
            tcp = (
                tcp
                + "  local-endpoints-table-buckets {:d}".format((aos + pos) // 4)
                + "\n"
            )
            tcp = tcp + "  local-endpoints-table-memory 3g\n"
        tcp = tcp + "}\n\n"

        tcp = tcp + "tcp {\n"
        tcp = tcp + "  preallocated-connections {:d}".format(aos + pos) + "\n"
        if aos > 0:
            tcp = tcp + "  preallocated-half-open-connections {:d}".format(aos) + "\n"
        tcp = tcp + "}\n\n"

        return tcp.rstrip("\n")

    def apply_vpp_startup(self):
        """
        Apply the vpp startup configration

        """

        # Apply the VPP startup configruation
        for i in self._nodes.items():
            node = i[1]

            # Get the startup file
            rootdir = node["rootdir"]
            sfile = rootdir + node["vpp"]["startup_config_file"]

            # Get the buffers
            devices = self._apply_vpp_devices(node)

            # Get the CPU config
            cpu = self._apply_vpp_cpu(node)

            # Get the buffer configuration
            buffers = self._apply_buffers(node)
            # Get the TCP configuration, if any
            tcp = self._apply_vpp_tcp(node)

            # Make a backup if needed
            self._autoconfig_backup_file(sfile)

            # Get the template
            tfile = sfile + ".template"
            (ret, stdout, stderr) = VPPUtil.exec_command("cat {}".format(tfile))
            if ret != 0:
                raise RuntimeError(
                    "Executing cat command failed to node {}".format(node["host"])
                )
            startup = stdout.format(cpu=cpu, buffers=buffers, devices=devices, tcp=tcp)

            (ret, stdout, stderr) = VPPUtil.exec_command("rm {}".format(sfile))
            if ret != 0:
                logging.debug(stderr)

            cmd = "sudo cat > {0} << EOF\n{1}\n".format(sfile, startup)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                raise RuntimeError("Writing config failed node {}".format(node["host"]))

    def apply_grub_cmdline(self):
        """
        Apply the grub cmdline

        """

        for i in self._nodes.items():
            node = i[1]

            # Get the isolated CPUs
            other_workers = node["cpu"]["other_workers"]
            vpp_workers = node["cpu"]["vpp_workers"]
            if "vpp_main_core" in node["cpu"]:
                vpp_main_core = node["cpu"]["vpp_main_core"]
            else:
                vpp_main_core = 0
            all_workers = []
            if other_workers is not None:
                all_workers = [other_workers]
            if vpp_main_core != 0:
                all_workers += [(vpp_main_core, vpp_main_core)]
            all_workers += vpp_workers
            isolated_cpus = ""
            for idx, worker in enumerate(all_workers):
                if worker is None:
                    continue
                if idx > 0:
                    isolated_cpus += ","
                if worker[0] == worker[1]:
                    isolated_cpus += "{}".format(worker[0])
                else:
                    isolated_cpus += "{}-{}".format(worker[0], worker[1])

            vppgrb = VppGrubUtil(node)
            current_cmdline = vppgrb.get_current_cmdline()
            if "grub" not in node:
                node["grub"] = {}
            node["grub"]["current_cmdline"] = current_cmdline
            node["grub"]["default_cmdline"] = vppgrb.apply_cmdline(node, isolated_cpus)

        self.updateconfig()

    def get_hugepages(self):
        """
        Get the hugepage configuration

        """

        for i in self._nodes.items():
            node = i[1]

            hpg = VppHugePageUtil(node)
            max_map_count, shmmax = hpg.get_huge_page_config()
            node["hugepages"]["max_map_count"] = max_map_count
            node["hugepages"]["shmax"] = shmmax
            total, free, size, memtotal, memfree = hpg.get_actual_huge_pages()
            node["hugepages"]["actual_total"] = total
            node["hugepages"]["free"] = free
            node["hugepages"]["size"] = size
            node["hugepages"]["memtotal"] = memtotal
            node["hugepages"]["memfree"] = memfree

        self.updateconfig()

    def get_grub(self):
        """
        Get the grub configuration

        """

        for i in self._nodes.items():
            node = i[1]

            vppgrb = VppGrubUtil(node)
            current_cmdline = vppgrb.get_current_cmdline()
            default_cmdline = vppgrb.get_default_cmdline()

            # Get the total number of isolated CPUs
            current_iso_cpus = 0
            iso_cpur = re.findall(r"isolcpus=[\w+\-,]+", current_cmdline)
            iso_cpurl = len(iso_cpur)
            if iso_cpurl > 0:
                iso_cpu_str = iso_cpur[0]
                iso_cpu_str = iso_cpu_str.split("=")[1]
                iso_cpul = iso_cpu_str.split(",")
                for iso_cpu in iso_cpul:
                    isocpuspl = iso_cpu.split("-")
                    if len(isocpuspl) == 1:
                        current_iso_cpus += 1
                    else:
                        first = int(isocpuspl[0])
                        second = int(isocpuspl[1])
                        if first == second:
                            current_iso_cpus += 1
                        else:
                            current_iso_cpus += second - first

            if "grub" not in node:
                node["grub"] = {}
            node["grub"]["current_cmdline"] = current_cmdline
            node["grub"]["default_cmdline"] = default_cmdline
            node["grub"]["current_iso_cpus"] = current_iso_cpus

        self.updateconfig()

    @staticmethod
    def _get_device(node):
        """
        Get the device configuration for a single node

        :param node: Node dictionary with cpuinfo.
        :type node: dict

        """

        vpp = VppPCIUtil(node)
        vpp.get_all_devices()

        # Save the device information
        node["devices"] = {}
        node["devices"]["dpdk_devices"] = vpp.get_dpdk_devices()
        node["devices"]["kernel_devices"] = vpp.get_kernel_devices()
        node["devices"]["other_devices"] = vpp.get_other_devices()
        node["devices"]["linkup_devices"] = vpp.get_link_up_devices()

    def get_devices_per_node(self):
        """
        Get the device configuration for all the nodes

        """

        for i in self._nodes.items():
            node = i[1]
            # Update the interface data

            self._get_device(node)

        self.updateconfig()

    @staticmethod
    def get_cpu_layout(node):
        """
        Get the cpu layout

        using lscpu -p get the cpu layout.
        Returns a list with each item representing a single cpu.

        :param node: Node dictionary.
        :type node: dict
        :returns: The cpu layout
        :rtype: list
        """

        cmd = "lscpu -p"
        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
        if ret != 0:
            raise RuntimeError(
                "{} failed on node {} {}".format(cmd, node["host"], stderr)
            )

        pcpus = []
        lines = stdout.split("\n")
        for line in lines:
            if line == "" or line[0] == "#":
                continue
            linesplit = line.split(",")
            layout = {
                "cpu": linesplit[0],
                "core": linesplit[1],
                "socket": linesplit[2],
                "node": linesplit[3],
            }

            # cpu, core, socket, node
            pcpus.append(layout)

        return pcpus

    def get_cpu(self):
        """
        Get the cpu configuration

        """

        # Get the CPU layout
        CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)

        for i in self._nodes.items():
            node = i[1]

            # Get the cpu layout
            layout = self.get_cpu_layout(node)
            node["cpu"]["layout"] = layout

            cpuinfo = node["cpuinfo"]
            smt_enabled = CpuUtils.is_smt_enabled(cpuinfo)
            node["cpu"]["smt_enabled"] = smt_enabled

            # We don't want to write the cpuinfo
            node["cpuinfo"] = ""

        # Write the config
        self.updateconfig()

    def discover(self):
        """
        Get the current system configuration.

        """

        # Get the Huge Page configuration
        self.get_hugepages()

        # Get the device configuration
        self.get_devices_per_node()

        # Get the CPU configuration
        self.get_cpu()

        # Get the current grub cmdline
        self.get_grub()

    def _modify_cpu_questions(self, node, total_cpus, numa_nodes):
        """
        Ask the user questions related to the cpu configuration.

        :param node: Node dictionary
        :param total_cpus: The total number of cpus in the system
        :param numa_nodes: The list of numa nodes in the system
        :type node: dict
        :type total_cpus: int
        :type numa_nodes: list
        """

        print(
            "\nYour system has {} core(s) and {} Numa Nodes.".format(
                total_cpus, len(numa_nodes)
            )
        )
        print(
            "To begin, we suggest not reserving any cores for "
            "VPP or other processes."
        )
        print(
            "Then to improve performance start reserving cores and "
            "adding queues as needed."
        )

        # Leave 1 for the general system
        total_cpus -= 1
        max_vpp_cpus = min(total_cpus, 4)
        total_vpp_cpus = 0
        if max_vpp_cpus > 0:
            question = (
                "\nHow many core(s) shall we reserve for "
                "VPP [0-{}][0]? ".format(max_vpp_cpus)
            )
            total_vpp_cpus = self._ask_user_range(question, 0, max_vpp_cpus, 0)
            node["cpu"]["total_vpp_cpus"] = total_vpp_cpus

        total_other_cpus = 0
        max_other_cores = total_cpus - total_vpp_cpus
        if max_other_cores > 0:
            question = (
                "How many core(s) do you want to reserve for "
                "processes other than VPP? [0-{}][0]? ".format(str(max_other_cores))
            )
            total_other_cpus = self._ask_user_range(question, 0, max_other_cores, 0)
            node["cpu"]["total_other_cpus"] = total_other_cpus

        max_main_cpus = total_cpus - total_vpp_cpus - total_other_cpus
        reserve_vpp_main_core = False
        if max_main_cpus > 0:
            question = "Should we reserve 1 core for the VPP Main thread? "
            question += "[y/N]? "
            answer = self._ask_user_yn(question, "n")
            if answer == "y":
                reserve_vpp_main_core = True
            node["cpu"]["reserve_vpp_main_core"] = reserve_vpp_main_core
            node["cpu"]["vpp_main_core"] = 0

        question = (
            "How many RX queues per port shall we use for "
            "VPP [1-4][1]? ".format(max_vpp_cpus)
        )
        total_rx_queues = self._ask_user_range(question, 1, 4, 1)
        node["cpu"]["total_rx_queues"] = total_rx_queues

    def modify_cpu(self, ask_questions=True):
        """
        Modify the cpu configuration, asking for the user for the values.

        :param ask_questions: When true ask the user for config parameters

        """

        # Get the CPU layout
        CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)

        for i in self._nodes.items():
            node = i[1]
            total_cpus = 0
            total_cpus_per_slice = 0
            cpus_per_node = {}
            numa_nodes = []
            cores = []
            cpu_layout = self.get_cpu_layout(node)

            # Assume the number of cpus per slice is always the same as the
            # first slice
            first_node = "0"
            for cpu in cpu_layout:
                if cpu["node"] != first_node:
                    break
                total_cpus_per_slice += 1

            # Get the total number of cpus, cores, and numa nodes from the
            # cpu layout
            for cpul in cpu_layout:
                numa_node = cpul["node"]
                core = cpul["core"]
                cpu = cpul["cpu"]
                total_cpus += 1

                if numa_node not in cpus_per_node:
                    cpus_per_node[numa_node] = []
                cpuperslice = int(cpu) % total_cpus_per_slice
                if cpuperslice == 0:
                    cpus_per_node[numa_node].append(
                        (int(cpu), int(cpu) + total_cpus_per_slice - 1)
                    )
                if numa_node not in numa_nodes:
                    numa_nodes.append(numa_node)
                if core not in cores:
                    cores.append(core)
            node["cpu"]["cpus_per_node"] = cpus_per_node

            # Ask the user some questions
            if ask_questions and total_cpus >= 4:
                self._modify_cpu_questions(node, total_cpus, numa_nodes)

            # Populate the interfaces with the numa node
            if "interfaces" in node:
                ikeys = node["interfaces"].keys()
                VPPUtil.get_interfaces_numa_node(node, *tuple(ikeys))

            # We don't want to write the cpuinfo
            node["cpuinfo"] = ""

        # Write the configs
        self._update_auto_config()
        self.updateconfig()

    def _modify_other_devices(self, node, other_devices, kernel_devices, dpdk_devices):
        """
        Modify the devices configuration, asking for the user for the values.

        """

        odevices_len = len(other_devices)
        if odevices_len > 0:
            print(
                "\nThese device(s) are currently NOT being used " "by VPP or the OS.\n"
            )
            VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
            question = "\nWould you like to give any of these devices"
            question += " back to the OS [Y/n]? "
            answer = self._ask_user_yn(question, "Y")
            if answer == "y":
                vppd = {}
                for dit in other_devices.items():
                    dvid = dit[0]
                    device = dit[1]
                    question = "Would you like to use device {} for".format(dvid)
                    question += " the OS [y/N]? "
                    answer = self._ask_user_yn(question, "n")
                    if answer == "y":
                        if (
                            "unused" in device
                            and len(device["unused"]) != 0
                            and device["unused"][0] != ""
                        ):
                            driver = device["unused"][0]
                            ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
                            if ret:
                                logging.debug("Could not bind device {}".format(dvid))
                            else:
                                vppd[dvid] = device
                for dit in vppd.items():
                    dvid = dit[0]
                    device = dit[1]
                    kernel_devices[dvid] = device
                    del other_devices[dvid]

        odevices_len = len(other_devices)
        if odevices_len > 0:
            print("\nThese device(s) are still NOT being used " "by VPP or the OS.\n")
            VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
            question = "\nWould you like use any of these for VPP [y/N]? "
            answer = self._ask_user_yn(question, "N")
            if answer == "y":
                vppd = {}
                for dit in other_devices.items():
                    dvid = dit[0]
                    device = dit[1]
                    question = "Would you like to use device {} ".format(dvid)
                    question += "for VPP [y/N]? "
                    answer = self._ask_user_yn(question, "n")
                    if answer == "y":
                        vppd[dvid] = device
                for dit in vppd.items():
                    dvid = dit[0]
                    device = dit[1]
                    if (
                        "unused" in device
                        and len(device["unused"]) != 0
                        and device["unused"][0] != ""
                    ):
                        driver = device["unused"][0]
                        logging.debug(
                            "Binding device {} to driver {}".format(dvid, driver)
                        )
                        ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
                        if ret:
                            logging.debug("Could not bind device {}".format(dvid))
                        else:
                            dpdk_devices[dvid] = device
                            del other_devices[dvid]

    def update_interfaces_config(self):
        """
        Modify the interfaces directly from the config file.

        """

        for i in self._nodes.items():
            node = i[1]
            devices = node["devices"]
            all_devices = devices["other_devices"]
            all_devices.update(devices["dpdk_devices"])
            all_devices.update(devices["kernel_devices"])

            current_ifcs = {}
            interfaces = {}
            if "interfaces" in node:
                current_ifcs = node["interfaces"]
            if current_ifcs:
                for ifc in current_ifcs.values():
                    dvid = ifc["pci_address"]
                    if dvid in all_devices:
                        VppPCIUtil.vpp_create_interface(
                            interfaces, dvid, all_devices[dvid]
                        )
            node["interfaces"] = interfaces

        self.updateconfig()

    def modify_devices(self):
        """
        Modify the devices configuration, asking for the user for the values.

        """

        for i in self._nodes.items():
            node = i[1]
            devices = node["devices"]
            other_devices = devices["other_devices"]
            kernel_devices = devices["kernel_devices"]
            dpdk_devices = devices["dpdk_devices"]

            if other_devices:
                self._modify_other_devices(
                    node, other_devices, kernel_devices, dpdk_devices
                )

                # Get the devices again for this node
                self._get_device(node)
                devices = node["devices"]
                kernel_devices = devices["kernel_devices"]
                dpdk_devices = devices["dpdk_devices"]

            klen = len(kernel_devices)
            if klen > 0:
                print("\nThese devices are safe to be used with VPP.\n")
                VppPCIUtil.show_vpp_devices(kernel_devices)
                question = (
                    "\nWould you like to use any of these " "device(s) for VPP [y/N]? "
                )
                answer = self._ask_user_yn(question, "n")
                if answer == "y":
                    vppd = {}
                    for dit in kernel_devices.items():
                        dvid = dit[0]
                        device = dit[1]
                        question = "Would you like to use device {} ".format(dvid)
                        question += "for VPP [y/N]? "
                        answer = self._ask_user_yn(question, "n")
                        if answer == "y":
                            vppd[dvid] = device
                    for dit in vppd.items():
                        dvid = dit[0]
                        device = dit[1]
                        if (
                            "unused" in device
                            and len(device["unused"]) != 0
                            and device["unused"][0] != ""
                        ):
                            driver = device["unused"][0]
                            question = "Would you like to bind the driver {} for {} [y/N]? ".format(
                                driver, dvid
                            )
                            answer = self._ask_user_yn(question, "n")
                            if answer == "y":
                                logging.debug(
                                    "Binding device {} to driver {}".format(
                                        dvid, driver
                                    )
                                )
                                ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
                                if ret:
                                    logging.debug(
                                        "Could not bind device {}".format(dvid)
                                    )
                        dpdk_devices[dvid] = device
                        del kernel_devices[dvid]

            dlen = len(dpdk_devices)
            if dlen > 0:
                print("\nThese device(s) are already using DPDK.\n")
                VppPCIUtil.show_vpp_devices(dpdk_devices, show_interfaces=False)
                question = "\nWould you like to remove any of "
                question += "these device(s) [y/N]? "
                answer = self._ask_user_yn(question, "n")
                if answer == "y":
                    vppdl = {}
                    for dit in dpdk_devices.items():
                        dvid = dit[0]
                        device = dit[1]
                        question = "Would you like to remove {} [y/N]? ".format(dvid)
                        answer = self._ask_user_yn(question, "n")
                        if answer == "y":
                            vppdl[dvid] = device
                    for dit in vppdl.items():
                        dvid = dit[0]
                        device = dit[1]
                        if (
                            "unused" in device
                            and len(device["unused"]) != 0
                            and device["unused"][0] != ""
                        ):
                            driver = device["unused"][0]
                            logging.debug(
                                "Binding device {} to driver {}".format(dvid, driver)
                            )
                            ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
                            if ret:
                                logging.debug("Could not bind device {}".format(dvid))
                            else:
                                kernel_devices[dvid] = device
                                del dpdk_devices[dvid]

            interfaces = {}
            for dit in dpdk_devices.items():
                dvid = dit[0]
                device = dit[1]
                VppPCIUtil.vpp_create_interface(interfaces, dvid, device)
            node["interfaces"] = interfaces

        self._update_auto_config()
        self.updateconfig()

    def modify_huge_pages(self):
        """
        Modify the huge page configuration, asking for the user for the values.

        """

        for i in self._nodes.items():
            node = i[1]

            total = node["hugepages"]["actual_total"]
            free = node["hugepages"]["free"]
            size = node["hugepages"]["size"]
            memfree = node["hugepages"]["memfree"].split(" ")[0]
            hugesize = int(size.split(" ")[0])
            # The max number of huge pages should be no more than
            # 70% of total free memory
            maxpages = (int(memfree) * MAX_PERCENT_FOR_HUGE_PAGES // 100) // hugesize
            print("\nThere currently {} {} huge pages free.".format(free, size))
            question = "Do you want to reconfigure the number of " "huge pages [y/N]? "
            answer = self._ask_user_yn(question, "n")
            if answer == "n":
                node["hugepages"]["total"] = total
                continue

            print("\nThere currently a total of {} huge pages.".format(total))
            question = "How many huge pages do you want [{} - {}][{}]? ".format(
                MIN_TOTAL_HUGE_PAGES, maxpages, MIN_TOTAL_HUGE_PAGES
            )
            answer = self._ask_user_range(question, 1024, maxpages, 1024)
            node["hugepages"]["total"] = str(answer)

        # Update auto-config.yaml
        self._update_auto_config()

        # Rediscover just the hugepages
        self.get_hugepages()

    def get_tcp_params(self):
        """
        Get the tcp configuration

        """
        # maybe nothing to do here?
        self.updateconfig()

    def acquire_tcp_params(self):
        """
        Ask the user for TCP stack configuration parameters

        """

        for i in self._nodes.items():
            node = i[1]

            question = (
                "\nHow many active-open / tcp client sessions are "
                "expected [0-10000000][0]? "
            )
            answer = self._ask_user_range(question, 0, 10000000, 0)
            # Less than 10K is equivalent to 0
            if int(answer) < 10000:
                answer = 0
            node["tcp"]["active_open_sessions"] = answer

            question = (
                "How many passive-open / tcp server sessions are "
                "expected [0-10000000][0]? "
            )
            answer = self._ask_user_range(question, 0, 10000000, 0)
            # Less than 10K is equivalent to 0
            if int(answer) < 10000:
                answer = 0
            node["tcp"]["passive_open_sessions"] = answer

        # Update auto-config.yaml
        self._update_auto_config()

        # Rediscover tcp parameters
        self.get_tcp_params()

    @staticmethod
    def patch_qemu(node):
        """
        Patch qemu with the correct patches.

        :param node: Node dictionary
        :type node: dict
        """

        print('\nWe are patching the node "{}":\n'.format(node["host"]))
        QemuUtils.build_qemu(node, force_install=True, apply_patch=True)

    @staticmethod
    def cpu_info(node):
        """
        print the CPU information

        """

        cpu = CpuUtils.get_cpu_info_per_node(node)

        item = "Model name"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "CPU(s)"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "Thread(s) per core"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "Core(s) per socket"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "Socket(s)"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "NUMA node(s)"
        numa_nodes = 0
        if item in cpu:
            numa_nodes = int(cpu[item])
        for i in range(0, numa_nodes):
            item = "NUMA node{} CPU(s)".format(i)
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "CPU max MHz"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = "CPU min MHz"
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))

        if node["cpu"]["smt_enabled"]:
            smt = "Enabled"
        else:
            smt = "Disabled"
        print("{:>20}:    {}".format("SMT", smt))

        # VPP Threads
        print("\nVPP Threads: (Name: Cpu Number)")
        vpp_processes = cpu["vpp_processes"]
        for i in vpp_processes.items():
            print("  {:10}: {:4}".format(i[0], i[1]))

    @staticmethod
    def device_info(node):
        """
        Show the device information.

        """

        if "cpu" in node and "total_mbufs" in node["cpu"]:
            total_mbufs = node["cpu"]["total_mbufs"]
            if total_mbufs != 0:
                print("Total Number of Buffers: {}".format(total_mbufs))

        vpp = VppPCIUtil(node)
        vpp.get_all_devices()
        linkup_devs = vpp.get_link_up_devices()
        if len(linkup_devs):
            print("\nDevices with link up (can not be used with VPP):")
            vpp.show_vpp_devices(linkup_devs, show_header=False)
            # for dev in linkup_devs:
            #    print ("    " + dev)
        kernel_devs = vpp.get_kernel_devices()
        if len(kernel_devs):
            print("\nDevices bound to kernel drivers:")
            vpp.show_vpp_devices(kernel_devs, show_header=False)
        else:
            print("\nNo devices bound to kernel drivers")

        dpdk_devs = vpp.get_dpdk_devices()
        if len(dpdk_devs):
            print("\nDevices bound to DPDK drivers:")
            vpp.show_vpp_devices(dpdk_devs, show_interfaces=True, show_header=False)
        else:
            print("\nNo devices bound to DPDK drivers")

        other_devs = vpp.get_other_devices()
        if len(other_devs):
            print("\nDevices not bound to Kernel or DPDK drivers:")
            vpp.show_vpp_devices(other_devs, show_interfaces=True, show_header=False)
        else:
            print("\nNo devices not bound to Kernel or DPDK drivers")

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return

        print("\nDevices in use by VPP:")

        if len(interfaces.items()) < 2:
            print("None")
            return

        print(
            "{:30} {:4} {:4} {:7} {:4} {:7}".format(
                "Name", "Numa", "RXQs", "RXDescs", "TXQs", "TXDescs"
            )
        )
        for intf in sorted(interfaces.items()):
            name = intf[0]
            value = intf[1]
            if name == "local0":
                continue
            numa = rx_qs = rx_ds = tx_qs = tx_ds = ""
            if "numa" in value:
                numa = int(value["numa"])
            if "rx queues" in value:
                rx_qs = int(value["rx queues"])
            if "rx descs" in value:
                rx_ds = int(value["rx descs"])
            if "tx queues" in value:
                tx_qs = int(value["tx queues"])
            if "tx descs" in value:
                tx_ds = int(value["tx descs"])

            print(
                "{:30} {:>4} {:>4} {:>7} {:>4} {:>7}".format(
                    name, numa, rx_qs, rx_ds, tx_qs, tx_ds
                )
            )

    @staticmethod
    def hugepage_info(node):
        """
        Show the huge page information.

        """

        hpg = VppHugePageUtil(node)
        hpg.show_huge_pages()

    @staticmethod
    def has_interfaces(node):
        """
        Check for interfaces, return tru if there is at least one

        :returns: boolean
        """
        if "interfaces" in node and len(node["interfaces"]):
            return True
        else:
            return False

    @staticmethod
    def min_system_resources(node):
        """
        Check the system for basic minimum resources, return true if
        there is enough.

        :returns: boolean
        """

        min_sys_res = True

        # CPUs
        if "layout" in node["cpu"]:
            total_cpus = len(node["cpu"]["layout"])
            if total_cpus < 2:
                print(
                    "\nThere is only {} CPU(s) available on this system. "
                    "This is not enough to run VPP.".format(total_cpus)
                )
                min_sys_res = False

        # System Memory
        if (
            "free" in node["hugepages"]
            and "memfree" in node["hugepages"]
            and "size" in node["hugepages"]
        ):
            free = node["hugepages"]["free"]
            memfree = float(node["hugepages"]["memfree"].split(" ")[0])
            hugesize = float(node["hugepages"]["size"].split(" ")[0])

            memhugepages = MIN_TOTAL_HUGE_PAGES * hugesize
            percentmemhugepages = (memhugepages / memfree) * 100
            if free is "0" and percentmemhugepages > MAX_PERCENT_FOR_HUGE_PAGES:
                print(
                    "\nThe System has only {} of free memory. You will not "
                    "be able to allocate enough Huge Pages for VPP.".format(
                        int(memfree)
                    )
                )
                min_sys_res = False

        return min_sys_res

    def sys_info(self):
        """
        Print the system information

        """

        for i in self._nodes.items():
            print("\n==============================")
            name = i[0]
            node = i[1]

            print("NODE: {}\n".format(name))

            # CPU
            print("CPU:")
            self.cpu_info(node)

            # Grub
            print("\nGrub Command Line:")
            if "grub" in node:
                print("  Current: {}".format(node["grub"]["current_cmdline"]))
                print("  Configured: {}".format(node["grub"]["default_cmdline"]))

            # Huge Pages
            print("\nHuge Pages:")
            self.hugepage_info(node)

            # Devices
            print("\nDevices:")
            self.device_info(node)

            # Status
            print("\nVPP Service Status:")
            state, errors = VPPUtil.status(node)
            print("  {}".format(state))
            for e in errors:
                print("  {}".format(e))

            # Minimum system resources
            self.min_system_resources(node)

            print("\n==============================")

    def _ipv4_interface_setup_questions(self, node):
        """
        Ask the user some questions and get a list of interfaces
        and IPv4 addresses associated with those interfaces

        :param node: Node dictionary.
        :type node: dict
        :returns: A list or interfaces with ip addresses
        :rtype: dict
        """

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return

        interfaces_with_ip = []
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name == "local0":
                continue

            question = "Would you like add address to " "interface {} [Y/n]? ".format(
                name
            )
            answer = self._ask_user_yn(question, "y")
            if answer == "y":
                address = {}
                addr = self._ask_user_ipv4()
                address["name"] = name
                address["addr"] = addr
                interfaces_with_ip.append(address)

        return interfaces_with_ip

    def ipv4_interface_setup(self):
        """
        After asking the user some questions, get a list of interfaces
        and IPv4 addresses associated with those interfaces

        """

        for i in self._nodes.items():
            node = i[1]

            # Show the current interfaces with IP addresses
            current_ints = VPPUtil.get_int_ip(node)
            if current_ints != {}:
                print("\nThese are the current interfaces with IP addresses:")
                for items in sorted(current_ints.items()):
                    name = items[0]
                    value = items[1]
                    if "address" not in value:
                        address = "Not Set"
                    else:
                        address = value["address"]
                    print("{:30} {:20} {:10}".format(name, address, value["state"]))
                question = "\nWould you like to keep this configuration " "[Y/n]? "
                answer = self._ask_user_yn(question, "y")
                if answer == "y":
                    continue
            else:
                print("\nThere are currently no interfaces with IP " "addresses.")

            # Create a script that add the ip addresses to the interfaces
            # and brings the interfaces up
            ints_with_addrs = self._ipv4_interface_setup_questions(node)
            content = ""
            for ints in ints_with_addrs:
                name = ints["name"]
                addr = ints["addr"]
                setipstr = "set int ip address {} {}\n".format(name, addr)
                setintupstr = "set int state {} up\n".format(name)
                content += setipstr + setintupstr

            # Write the content to the script
            rootdir = node["rootdir"]
            filename = rootdir + "/vpp/vpp-config/scripts/set_int_ipv4_and_up"
            with open(filename, "w+") as sfile:
                sfile.write(content)

            # Execute the script
            cmd = "vppctl exec {}".format(filename)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)

            print("\nA script as been created at {}".format(filename))
            print("This script can be run using the following:")
            print("vppctl exec {}\n".format(filename))

    def _create_vints_questions(self, node):
        """
        Ask the user some questions and get a list of interfaces
        and IPv4 addresses associated with those interfaces

        :param node: Node dictionary.
        :type node: dict
        :returns: A list or interfaces with ip addresses
        :rtype: list
        """

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []

        # First delete all the Virtual interfaces
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name[:7] == "Virtual":
                cmd = "vppctl delete vhost-user {}".format(name)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    logging.debug(
                        "{} failed on node {} {}".format(cmd, node["host"], stderr)
                    )

        # Create a virtual interface, for each interface the user wants to use
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []
        interfaces_with_virtual_interfaces = []
        inum = 1
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name == "local0":
                continue

            question = (
                "Would you like connect this interface {} to "
                "the VM [Y/n]? ".format(name)
            )
            answer = self._ask_user_yn(question, "y")
            if answer == "y":
                sockfilename = "/var/run/vpp/{}.sock".format(name.replace("/", "_"))
                if os.path.exists(sockfilename):
                    os.remove(sockfilename)
                cmd = "vppctl create vhost-user socket {} server".format(sockfilename)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    raise RuntimeError(
                        "Couldn't execute the command {}, {}.".format(cmd, stderr)
                    )
                vintname = stdout.rstrip("\r\n")

                cmd = "chmod 777 {}".format(sockfilename)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    raise RuntimeError(
                        "Couldn't execute the command {}, {}.".format(cmd, stderr)
                    )

                interface = {
                    "name": name,
                    "virtualinterface": "{}".format(vintname),
                    "bridge": "{}".format(inum),
                }
                inum += 1
                interfaces_with_virtual_interfaces.append(interface)

        return interfaces_with_virtual_interfaces

    def create_and_bridge_virtual_interfaces(self):
        """
        After asking the user some questions, create a VM and connect
        the interfaces to VPP interfaces

        """

        for i in self._nodes.items():
            node = i[1]

            # Show the current bridge and interface configuration
            print("\nThis the current bridge configuration:")
            VPPUtil.show_bridge(node)
            question = "\nWould you like to keep this configuration [Y/n]? "
            answer = self._ask_user_yn(question, "y")
            if answer == "y":
                continue

            # Create a script that builds a bridge configuration with
            # physical interfaces and virtual interfaces
            ints_with_vints = self._create_vints_questions(node)
            content = ""
            for intf in ints_with_vints:
                vhoststr = "\n".join(
                    [
                        "comment { The following command creates the socket }",
                        "comment { and returns a virtual interface }",
                        "comment {{ create vhost-user socket "
                        "/var/run/vpp/sock{}.sock server }}\n".format(intf["bridge"]),
                    ]
                )

                setintdnstr = "set interface state {} down\n".format(intf["name"])

                setintbrstr = "set interface l2 bridge {} {}\n".format(
                    intf["name"], intf["bridge"]
                )
                setvintbrstr = "set interface l2 bridge {} {}\n".format(
                    intf["virtualinterface"], intf["bridge"]
                )

                # set interface state VirtualEthernet/0/0/0 up
                setintvststr = "set interface state {} up\n".format(
                    intf["virtualinterface"]
                )

                # set interface state VirtualEthernet/0/0/0 down
                setintupstr = "set interface state {} up\n".format(intf["name"])

                content += (
                    vhoststr
                    + setintdnstr
                    + setintbrstr
                    + setvintbrstr
                    + setintvststr
                    + setintupstr
                )

            # Write the content to the script
            rootdir = node["rootdir"]
            filename = rootdir + "/vpp/vpp-config/scripts/create_vms_and_connect_to_vpp"
            with open(filename, "w+") as sfile:
                sfile.write(content)

            # Execute the script
            cmd = "vppctl exec {}".format(filename)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)

            print("\nA script as been created at {}".format(filename))
            print("This script can be run using the following:")
            print("vppctl exec {}\n".format(filename))

    def _iperf_vm_questions(self, node):
        """
        Ask the user some questions and get a list of interfaces
        and IPv4 addresses associated with those interfaces

        :param node: Node dictionary.
        :type node: dict
        :returns: A list or interfaces with ip addresses
        :rtype: list
        """

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []

        # First delete all the Virtual interfaces
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name[:7] == "Virtual":
                cmd = "vppctl delete vhost-user {}".format(name)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    logging.debug(
                        "{} failed on node {} {}".format(cmd, node["host"], stderr)
                    )

        # Create a virtual interface, for each interface the user wants to use
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []
        interfaces_with_virtual_interfaces = []
        inum = 1

        while True:
            print("\nPlease pick one interface to connect to the iperf VM.")
            for intf in sorted(interfaces.items()):
                name = intf[0]
                if name == "local0":
                    continue

                question = (
                    "Would you like connect this interface {} to "
                    "the VM [y/N]? ".format(name)
                )
                answer = self._ask_user_yn(question, "n")
                if answer == "y":
                    self._sockfilename = "/var/run/vpp/{}.sock".format(
                        name.replace("/", "_")
                    )
                    if os.path.exists(self._sockfilename):
                        os.remove(self._sockfilename)
                    cmd = "vppctl create vhost-user socket {} server".format(
                        self._sockfilename
                    )
                    (ret, stdout, stderr) = vpputl.exec_command(cmd)
                    if ret != 0:
                        raise RuntimeError(
                            "Couldn't execute the command {}, {}.".format(cmd, stderr)
                        )
                    vintname = stdout.rstrip("\r\n")

                    cmd = "chmod 777 {}".format(self._sockfilename)
                    (ret, stdout, stderr) = vpputl.exec_command(cmd)
                    if ret != 0:
                        raise RuntimeError(
                            "Couldn't execute the command {}, {}.".format(cmd, stderr)
                        )

                    interface = {
                        "name": name,
                        "virtualinterface": "{}".format(vintname),
                        "bridge": "{}".format(inum),
                    }
                    inum += 1
                    interfaces_with_virtual_interfaces.append(interface)
                    return interfaces_with_virtual_interfaces

    def create_and_bridge_iperf_virtual_interface(self):
        """
        After asking the user some questions, and create and bridge a
        virtual interface to be used with iperf VM

        """

        for i in self._nodes.items():
            node = i[1]

            # Show the current bridge and interface configuration
            print("\nThis the current bridge configuration:")
            ifaces = VPPUtil.show_bridge(node)
            question = "\nWould you like to keep this configuration [Y/n]? "
            answer = self._ask_user_yn(question, "y")
            if answer == "y":
                self._sockfilename = "/var/run/vpp/{}.sock".format(
                    ifaces[0]["name"].replace("/", "_")
                )
                if os.path.exists(self._sockfilename):
                    continue

            # Create a script that builds a bridge configuration with
            # physical interfaces and virtual interfaces
            ints_with_vints = self._iperf_vm_questions(node)
            content = ""
            for intf in ints_with_vints:
                vhoststr = "\n".join(
                    [
                        "comment { The following command creates the socket }",
                        "comment { and returns a virtual interface }",
                        "comment {{ create vhost-user socket "
                        "/var/run/vpp/sock{}.sock server }}\n".format(intf["bridge"]),
                    ]
                )

                setintdnstr = "set interface state {} down\n".format(intf["name"])

                setintbrstr = "set interface l2 bridge {} {}\n".format(
                    intf["name"], intf["bridge"]
                )
                setvintbrstr = "set interface l2 bridge {} {}\n".format(
                    intf["virtualinterface"], intf["bridge"]
                )

                # set interface state VirtualEthernet/0/0/0 up
                setintvststr = "set interface state {} up\n".format(
                    intf["virtualinterface"]
                )

                # set interface state VirtualEthernet/0/0/0 down
                setintupstr = "set interface state {} up\n".format(intf["name"])

                content += (
                    vhoststr
                    + setintdnstr
                    + setintbrstr
                    + setvintbrstr
                    + setintvststr
                    + setintupstr
                )

            # Write the content to the script
            rootdir = node["rootdir"]
            filename = rootdir + "/vpp/vpp-config/scripts/create_iperf_vm"
            with open(filename, "w+") as sfile:
                sfile.write(content)

            # Execute the script
            cmd = "vppctl exec {}".format(filename)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)

            print("\nA script as been created at {}".format(filename))
            print("This script can be run using the following:")
            print("vppctl exec {}\n".format(filename))

    @staticmethod
    def destroy_iperf_vm(name):
        """
        After asking the user some questions, create a VM and connect
        the interfaces to VPP interfaces

        :param name: The name of the VM to be be destroyed
        :type name: str
        """

        cmd = "virsh list"
        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
        if ret != 0:
            logging.debug(stderr)
            raise RuntimeError(
                "Couldn't execute the command {} : {}".format(cmd, stderr)
            )

        if re.findall(name, stdout):
            cmd = "virsh destroy {}".format(name)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)
                raise RuntimeError(
                    "Couldn't execute the command {} : {}".format(cmd, stderr)
                )

    def create_iperf_vm(self, vmname):
        """
        After asking the user some questions, create a VM and connect
        the interfaces to VPP interfaces

        """

        # Read the iperf VM template file
        distro = VPPUtil.get_linux_distro()
        if distro[0] == "Ubuntu":
            tfilename = "{}/vpp/vpp-config/configs/iperf-ubuntu.xml.template".format(
                self._rootdir
            )
        else:
            tfilename = "{}/vpp/vpp-config/configs/iperf-centos.xml.template".format(
                self._rootdir
            )

        with open(tfilename, "r") as tfile:
            tcontents = tfile.read()
        tfile.close()

        # Add the variables
        imagename = "{}/vpp/vpp-config/{}".format(self._rootdir, IPERFVM_IMAGE)
        isoname = "{}/vpp/vpp-config/{}".format(self._rootdir, IPERFVM_ISO)
        tcontents = tcontents.format(
            vmname=vmname,
            imagename=imagename,
            isoname=isoname,
            vhostsocketname=self._sockfilename,
        )

        # Write the xml
        ifilename = "{}/vpp/vpp-config/{}".format(self._rootdir, IPERFVM_XML)
        with open(ifilename, "w+") as ifile:
            ifile.write(tcontents)
        ifile.close()

        cmd = "virsh create {}".format(ifilename)
        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
        if ret != 0:
            logging.debug(stderr)
            raise RuntimeError(
                "Couldn't execute the command {} : {}".format(cmd, stderr)
            )
